use std::{hash::Hasher, ops::BitAnd};

use serde::{Deserialize, Serialize};

use crate::{fatscdc::GEAR_MATRIX, rabin::{slide_one_byte, WINDOW_SIZE}};

use super::spooky_hash::SpookyHasher;

const GSIZE32BIT:u64= 0xffffffff;
const FEATURE_MI:[u64;45] = [
    5652377,
    24423617,
    16341615,
    3526234,
    654410,
    3124312,
    35,
    61,
    212,
    121,
    551,
    610,
    367,
    651,
    319,
    129,
    551,
    633,
    11,
    17,
    23,
    71,
    37,
    247,
    137,
    431,
    2137,
    173,
    523,
    753,
    143,
    372,
    637,
    821,
    107,
    423,
    667,
    851,
    453,
    964,
    910,
    223,
    827,
    751,
    456
];

const FEATURE_AI:[u64;45] = [
    45673472,
    146512310,
    142364781,
    1225434201,
    324130,
    359840125,
    15254201,
    41870,
    3340125,
    44122,
    8431641,
    824311,
    254201,
    7632410,
    7540125,
    115422,
    34431741,
    424311,
    31,
    5,
    37,
    97,
    241,
    257,
    2391,
    831,
    4313,
    3022467,
    4507221,
    6750467,
    412234,
    3174123,
    324123,
    25420451,
    31023,
    3512545,
    1541242,
    843541741,
    3244311,
    223401,
    3233410,
    354012523,
    12322,
    843178741,
    438911,
];

#[inline(always)]
pub fn spooky_hash64(u64_data:&[u64]) -> u64 {
    const SEED:u64 = 12345678;
    let byte_data = unsafe { u64_data.align_to::<u8>().1 };
    let mut spooky_hasher = SpookyHasher::new(SEED, SEED);
    spooky_hasher.write(byte_data);
    spooky_hasher.finish()
}

pub fn super_feature(data:&[u8]) -> [u64;3] {
    let mut fingerprint = 0;
    let mut window_pos = 0;
    let mut window = [0u8;WINDOW_SIZE];
    let mut features = [0u64;12];
    assert!(data.len() >= WINDOW_SIZE);
    for i in 0..47 {
        fingerprint = slide_one_byte(data[i], fingerprint, &mut window, &mut window_pos);
	}

	for i in 47..data.len() {
        fingerprint = slide_one_byte(data[i], fingerprint, &mut window, &mut window_pos);
        for k in 0..12 {
            let feature = fingerprint.wrapping_mul(FEATURE_MI[k]).wrapping_add(FEATURE_AI[k]).bitand(GSIZE32BIT); //&GSIZE32BIT;
            if feature > features[k] { features[k] = feature; }
        }
	}
	let sf1 = spooky_hash64(&features[0..4]);
	let sf2 = spooky_hash64(&features[4..8]);
	let sf3 = spooky_hash64(&features[8..12]);
    [sf1, sf2, sf3]
}

pub fn odess(data:&[u8]) -> [u64;3] {
    let mut fingerprint = 0;
    let mut features = [0u64;12];
    assert!(data.len() >= 32);

	for i in 0..31 {
		fingerprint = (fingerprint << 1) + GEAR_MATRIX[data[i] as usize];
	}

	for i in 31..data.len()
	{
		fingerprint = (fingerprint << 1) + GEAR_MATRIX[data[i] as usize];
		if (fingerprint & 0x000088035100) == 0 {
			for k in 0..12 {
				let feature = fingerprint.wrapping_mul(FEATURE_MI[k]).wrapping_add(FEATURE_AI[k]).bitand(GSIZE32BIT); //&GSIZE32BIT;
				if feature > features[k] { features[k] = feature; }
			}
		}
	}

	let sf1 = spooky_hash64(&features[0..4]);
	let sf2 = spooky_hash64(&features[4..8]);
	let sf3 = spooky_hash64(&features[8..12]);
    [sf1, sf2, sf3]
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum FinesseGroup {
    ThreeFeature,
    UnsortedFourFeature,
    SortedFourFeature,
    FiveFeature,
}

impl FinesseGroup {
    fn subregion_num(&self) -> usize {
        match self {
            Self::FiveFeature => 15,
            _ => 12,
        }
    }
}

pub fn finesse(data:&[u8], group:FinesseGroup) -> [u64;3] {
    let subregion_num = group.subregion_num();
    let subregion_size = data.len()/subregion_num;

    let mut fingerprint = 0;
    let mut window_pos = 0;
    let mut window = [0u8;WINDOW_SIZE];
    let mut features = [0u64;12];
    
    for i in 0..47 {
        fingerprint = slide_one_byte(data[i], fingerprint, &mut window, &mut window_pos);
	}

    let mut index = 47;
    let mut end_pos = 0;
    for k in 0..subregion_num {
        end_pos += subregion_size;
        end_pos = std::cmp::min(end_pos, data.len());
        while index < end_pos {
            fingerprint = slide_one_byte(data[index], fingerprint, &mut window, &mut window_pos);
            if fingerprint > features[k]{ features[k] = fingerprint; }
            index += 1;
        }
    }
    match group {
        FinesseGroup::ThreeFeature => three_features_per_sf(&mut features),
        FinesseGroup::UnsortedFourFeature => unsorted_four_features_per_sf(&features),
        FinesseGroup::SortedFourFeature => sorted_four_features_per_sf(&mut features),
        FinesseGroup::FiveFeature => five_features_per_sf(&features),
    }
}

fn find_max_feature(features:&[u64]) -> u64 {
    let mut max_feature = features[0];
    for &feature in features {
        max_feature = std::cmp::max(max_feature, feature);
    }
    max_feature
}


fn find_min_feature(features:&[u64]) -> u64 {
    let mut min_feature = features[0];
    for &feature in features {
        min_feature = std::cmp::min(min_feature, feature);
    }
    min_feature
}

fn three_features_per_sf(features:&mut [u64]) -> [u64;3] {
    let mut feature_buf = [0u64;3];
    feature_buf[0] = find_max_feature(&features[0..4]);
    feature_buf[1] = find_max_feature(&features[4..8]);
    feature_buf[2] = find_max_feature(&features[8..12]);
    let sf1 = spooky_hash64(&feature_buf);
    feature_buf[0] = find_min_feature(&features[0..4]);
    feature_buf[1] = find_min_feature(&features[4..8]);
    feature_buf[2] = find_min_feature(&features[8..12]);
    let sf2 = spooky_hash64(&feature_buf);
    features.sort();
    let sf3 = spooky_hash64(&features[0..3]);
    [sf1, sf2, sf3]
}

fn unsorted_four_features_per_sf(features:&[u64]) -> [u64;3] {
    let sf1 = spooky_hash64(&features[0..4]);
    let sf2 = spooky_hash64(&features[4..8]);
    let sf3 = spooky_hash64(&features[8..12]);
    [sf1, sf2, sf3]
}

fn sorted_four_features_per_sf(features:&mut [u64]) -> [u64;3] {
    features.sort();
    let sf1 = spooky_hash64(&features[0..4]);
    let sf2 = spooky_hash64(&features[4..8]);
    let sf3 = spooky_hash64(&features[8..12]);
    [sf1, sf2, sf3]
}

fn five_features_per_sf(features:&[u64]) -> [u64;3] {
    let mut feature_buf = [0u64;5];
    //feature_buf[0] = features[0..4].max
    seq!(N in 1..=3 {
        feature_buf[0] = features[N];
        feature_buf[1] = features[N+3];
        feature_buf[2] = features[N+6];
        feature_buf[3] = features[N+9];
        feature_buf[4] = features[N+12];
        let sf~N = spooky_hash64(&feature_buf);
    });
    [sf1, sf2, sf3]
}
